package com.javatest.classfile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author yutianhong
 * @version 1.0
 * @since 2023/11/29 20:30
 */
public class MyClassFileWriter {

    public static final byte CONSTANT_Class_info = 7;
    public static final byte CONSTANT_Fieldref_info = 9;
    public static final byte CONSTANT_Methodref_info = 10;
    public static final byte CONSTANT_InterfaceMethodref_info = 11;
    public static final byte CONSTANT_String_info = 8;
    public static final byte CONSTANT_Integer_info = 3;
    public static final byte CONSTANT_Float_info = 4;
    public static final byte CONSTANT_Long_info = 5;
    public static final byte CONSTANT_Double_info = 6;
    public static final byte CONSTANT_NameAndType_info = 12;
    public static final byte CONSTANT_Utf8_info = 1;

    public static final int OPCODE_ASTORE_1 = 0x4C;
    public static final int OPCODE_ALOAD_0 = 0x2A;
    public static final int OPCODE_ALOAD_1 = 0x2B;

    List<Byte> data = new ArrayList<>();

    public int length() {
        return data.size();
    }

    public MyClassFileWriter writeByte(byte b) {
        data.add(b);
        return this;
    }

    public MyClassFileWriter writeBytes(byte[] bytes) {
        for (byte b : bytes) {
            data.add(b);
        }
        return this;
    }

    public MyClassFileWriter writeAll(MyClassFileWriter writer) {
        this.data.addAll(writer.data);
        return this;
    }

    public MyClassFileWriter writeU1(int u1) {
        if ((u1 >> 8) != 0) {
            throw new IllegalArgumentException("u1 must < 0XFF00, but u1 = " + u1);
        }
        return writeU1((short) u1);
    }

    public MyClassFileWriter writeU1(short u1) {
        if ((u1 >> 8) != 0) {
            throw new IllegalArgumentException("u1 must < 0XFF00, but u1 = " + u1);
        }
        data.add(getByte(u1));
        return this;
    }

    public MyClassFileWriter writeU2(int u2) {
        if ((u2 >> 16) != 0) {
            throw new IllegalArgumentException("u2 must < 0XFFFF0000, but u2 = " + u2);
        }
        data.add(getByte((u2 >> 8)));
        data.add(getByte(u2));
        return this;
    }

    public MyClassFileWriter writeU4(long u4) {
        if ((u4 >> 32) != 0) {
            throw new IllegalArgumentException("u4 must < 0XFFFFFFFF00000000, but u4 = " + u4);
        }
        data.add(getByte((u4 >> 24)));
        data.add(getByte((u4 >> 16)));
        data.add(getByte((u4 >> 8)));
        data.add(getByte(u4));
        return this;
    }

    public void print() {
        for (int i = 0; i < data.size(); i++) {
            Byte b = data.get(i);
            System.out.printf("%02X", b);
            if (i % 8 == 7) System.out.print(" ");
            if (i % 32 == 31) System.out.println();
        }
        System.out.println();
    }

    public void writeClassFile(String path) {
        if (!path.endsWith(".class")) {
            throw new IllegalArgumentException("path must end with '.class', but path = " + path);
        }
        File file = new File(path);
        if (file.exists()) {
            file.delete();
        }
        try (FileOutputStream fos = new FileOutputStream(file)) {
            byte[] bytes = new byte[data.size()];
            for (int i = 0; i < bytes.length; i++) {
                bytes[i] = data.get(i);
            }
            fos.write(bytes);
            fos.flush();
            System.out.println("complete write data to class file: " + file.getAbsolutePath());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private byte getByte(short num) {
        return (byte) (num & 0XFF);
    }

    private byte getByte(int num) {
        return (byte) (num & 0XFF);
    }

    private byte getByte(long num) {
        return (byte) (num & 0XFF);
    }

}
